package org.odst;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import junit.framework.Assert;

import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.doc.table.OdfTableCell;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;

/**
 * @author Leonid Vysochyn
 */
public class CellsChecker extends Assert {

    Map<String, Object> propertyMap = new HashMap<String, Object>();

    public CellsChecker() {
    }

    boolean ignoreStyle = false;
    boolean ignoreFirstLastCellNums = false;

    public CellsChecker(Map<String, Object> propertyMap) {
        this.propertyMap = propertyMap;
    }

    public CellsChecker(Map<String, Object> propertyMap, boolean ignoreStyle) {
        this.propertyMap = propertyMap;
        this.ignoreStyle = ignoreStyle;
    }


    public boolean isIgnoreFirstLastCellNums() {
        return ignoreFirstLastCellNums;
    }

    public void setIgnoreFirstLastCellNums(boolean ignoreFirstLastCellNums) {
        this.ignoreFirstLastCellNums = ignoreFirstLastCellNums;
    }

    void checkSection(OdfTable srcSheet, OdfTable destSheet, int srcRowNum, int destRowNum, short fromCellNum, short toCellNum, int numberOfRows, boolean ignoreHeight, boolean ignoreNullRows) {
        for (int i = 0; i < numberOfRows; i++) {
            OdfTableRow sourceRow = srcSheet.getRowByIndex(srcRowNum + i);
            OdfTableRow destRow = destSheet.getRowByIndex(destRowNum + i);
            if (!ignoreNullRows) {
                assertTrue("Null Row problem found", (sourceRow != null && destRow != null) || (sourceRow == null && destRow == null));
                if (sourceRow != null) {
                    if (!ignoreHeight) {
                        assertEquals("Row height is not the same", sourceRow.getHeight(), destRow.getHeight());
                    }
                    checkCells(sourceRow, destRow, fromCellNum, toCellNum);
                }
            } else {
                if (!ignoreHeight) {
                    assertEquals("Row height is not the same", sourceRow.getHeight(), destRow.getHeight());
                }
                if (sourceRow == null && destRow != null) {
                    checkEmptyCells(destRow, fromCellNum, toCellNum);
                }
                if (destRow == null && sourceRow != null) {
                    checkEmptyCells(sourceRow, fromCellNum, toCellNum);
                }
                if (sourceRow != null && destRow != null) {
                    checkCells(sourceRow, destRow, fromCellNum, toCellNum);
                }
            }
        }
    }

    void checkRow(OdfTable sheet, int rowNum, int startCellNum, int endCellNum, Object[] values){
        OdfTableRow row  = sheet.getRowByIndex(rowNum);
        if( row != null){
            for(int i = startCellNum; i<=endCellNum; i++){
                OdfTableCell cell = row.getCellByIndex(i);
                if( cell != null ){
//                    Object cellValue = getCellValue(cell, values[i]);
//                    assertEquals("Result cell values incorrect in row=" + row + ", cell=" + i, values[i], cellValue);
                	checkCellValue(values[i], cell);
                }else{
                    fail("Cell is null");
                }
            }
        }else{
            fail("Row is null");
        }

    }

    private void checkEmptyCells(OdfTableRow destRow, short fromCellNum, short toCellNum) {
        if (destRow != null) {
            for (short i = fromCellNum; i <= toCellNum; i++) {
                try {
					assertNull("Cell " + i + " in " + destRow.getRowIndex() + " row is not null", destRow.getCellByIndex(i));
				} catch (IndexOutOfBoundsException e) {
					e.printStackTrace();
				}
            }
        }
    }

    void checkListCells(OdfTable srcSheet, int srcRowNum, OdfTable sheet, int startRowNum, short cellNum, Object[] values) {
    	OdfTableRow srcRow = srcSheet.getRowByIndex(srcRowNum);
    	OdfTableCell srcCell = null;
		try {
			srcCell = srcRow.getCellByIndex(cellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
        for (int i = 0; i < values.length; i++) {
        	OdfTableRow row = sheet.getRowByIndex(startRowNum + i);
        	OdfTableCell cell = null;
			try {
				cell = row.getCellByIndex(cellNum);
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
			}
            checkCellValue(values[i], cell);
            checkCellStyle(srcCell, cell);
        }
    }

    void checkFixedListCells(OdfTable srcSheet, int srcRowNum, OdfTable destSheet, int startRowNum, short cellNum, Object[] values) {
        for (int i = 0; i < values.length; i++) {
        	OdfTableRow srcRow = srcSheet.getRowByIndex(srcRowNum);
        	OdfTableCell srcCell = null;
			try {
				srcCell = srcRow.getCellByIndex(cellNum);
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
			}
			OdfTableRow destRow = destSheet.getRowByIndex(startRowNum + i);
			OdfTableCell destCell = null;
			try {
				destCell = destRow.getCellByIndex(cellNum);
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
			}
            checkCellValue(values[i], destCell);
            checkCellStyle(srcCell, destCell);
        }
    }

    void checkFormulaCell(OdfTable srcSheet, int srcRowNum, OdfTable destSheet, int destRowNum, short cellNum, String formula) {
    	OdfTableRow srcRow = srcSheet.getRowByIndex(srcRowNum);
    	OdfTableCell srcCell = null;
		try {
			srcCell = srcRow.getCellByIndex(cellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		OdfTableRow destRow = destSheet.getRowByIndex(destRowNum);
		OdfTableCell destCell = null;
		try {
			destCell = destRow.getCellByIndex(cellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
        checkCellStyle(srcCell, destCell);
        assertTrue("Result Cell is not a formula : " + destCell, destCell.getFormula() != null && !destCell.getFormula().isEmpty());
        assertEquals("Formula is incorrect : " + destCell, formula, destCell.getFormula());
    }

    void checkFormulaCell(OdfTable srcSheet, int srcRowNum, OdfTable destSheet, int destRowNum, short cellNum, String formula, boolean ignoreCellStyle) {
    	OdfTableRow srcRow = srcSheet.getRowByIndex(srcRowNum);
    	OdfTableCell srcCell = null;
		try {
			srcCell = srcRow.getCellByIndex(cellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		OdfTableRow destRow = destSheet.getRowByIndex(destRowNum);
		OdfTableCell destCell = null;
		try {
			destCell = destRow.getCellByIndex(cellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
        if (!ignoreCellStyle) {
            checkCellStyle(srcCell, destCell);
        }
        assertNotNull("Result Cell is not a formula", destCell.getFormula());
        assertEquals("Formula is incorrect", formula, destCell.getFormula());
    }

    void checkRows(OdfTable sourceSheet, OdfTable destSheet, int sourceRowNum, int destRowNum, int numberOfRows) {
        for (int i = 0; i < numberOfRows; i++) {
            OdfTableRow sourceRow = sourceSheet.getRowByIndex(sourceRowNum + i);
            OdfTableRow destRow = destSheet.getRowByIndex(destRowNum + i);
            assertTrue("Null Row problem found", (sourceRow != null && destRow != null) || (sourceRow == null && destRow == null));
            if (sourceRow != null && destRow != null) {
                if (!ignoreFirstLastCellNums) {
//                    assertEquals("First Cell Numbers differ in source and result row", sourceRow.getFirstCell().getCellAddress().Column, destRow.getFirstCell().getCellAddress().Column);
                }
//                assertEquals("Physical Number Of Cells differ in source and result row", sourceRow.getPhysicalNumberOfCells(), destRow.getPhysicalNumberOfCells());
                assertEquals("Row height is not the same for srcRow = " + sourceRow.getRowIndex() + ", destRow = " + destRow.getRowIndex(),
                        sourceRow.getHeight(), destRow.getHeight());
                checkCells(sourceRow, destRow, 0, sourceSheet.getColumnCount() - 1);
            }
        }
    }

    private void checkCells(OdfTableRow sourceRow, OdfTableRow resultRow, int startCell, int endCell) {
        for (int i = startCell; i <= endCell; i++) {
        	OdfTableCell sourceCell = null;
			try {
				sourceCell = sourceRow.getCellByIndex(i);
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
			}
			OdfTableCell resultCell = null;
			try {
				resultCell = resultRow.getCellByIndex(i);
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
			}
            assertTrue("Null cell problem found", (sourceCell != null && resultCell != null) || (sourceCell == null && resultCell == null));
            if (sourceCell != null) {
                checkCells(sourceCell, resultCell);
            }
        }
    }

    void checkCells(OdfTable srcSheet, OdfTable destSheet, int srcRowNum, short srcCellNum, int destRowNum, short destCellNum, boolean checkCellWidth) {
    	OdfTableRow srcRow = srcSheet.getRowByIndex(srcRowNum);
    	OdfTableRow destRow = destSheet.getRowByIndex(destRowNum);
        assertEquals("Row height is not the same", srcRow.getHeight(), destRow.getHeight());
        OdfTableCell srcCell = null;
		try {
			srcCell = srcRow.getCellByIndex(srcCellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		OdfTableCell destCell = null;
		try {
			destCell = destRow.getCellByIndex(destCellNum);
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
        assertTrue("Null cell problem found", (srcCell != null && destCell != null) || (srcCell == null && destCell == null));
        if (srcCell != null && destCell != null) {
            checkCells(srcCell, destCell);
        }
        if (checkCellWidth) {
            assertEquals("Cell Widths are different", getWidth(srcSheet, srcCellNum), getWidth(destSheet, destCellNum));
        }
    }

    static long getWidth(OdfTable sheet, int col) {
        long width = sheet.getColumnByIndex(col).getWidth();
//        if (width == sheet.getDefaultColumnWidth()) {
//            width = (short) (width * 256);
//        }
        return width;
    }


    private void checkCells(OdfTableCell sourceCell, OdfTableCell destCell) {
        checkCellValue(sourceCell, destCell);
        checkCellStyle(sourceCell, destCell);
    }

    private void checkCellStyle(OdfTableCell sourceCell, OdfTableCell destCell) {
        if (!ignoreStyle) {
//        	for (Property<?> property : sourceStyle.getProperties()) {
//        		Object value = sourceStyle.getValue(property.getName());
//        		if (value instanceof TableBorder) {
//        			TableBorder src = (TableBorder)value;
//        			TableBorder dest = (TableBorder)destStyle.getValue(property.getName());
//        			compareTableBorders(src, dest);
//        		} else if (value instanceof BorderLine) {
//        			BorderLine src = (BorderLine)value;
//        			BorderLine dest = (BorderLine)destStyle.getValue(property.getName());
//            		compareBorderLines(src, dest);
//        		} else if (value instanceof ShadowFormat) {
//        			ShadowFormat src = (ShadowFormat)value;
//        			ShadowFormat dest = (ShadowFormat)destStyle.getValue(property.getName());
//            		compareShadowFormats(src, dest);
//        		} else if (value instanceof CellProtection) {
//        			CellProtection src = (CellProtection)value;
//        			CellProtection dest = (CellProtection)destStyle.getValue(property.getName());
//            		compareCellProtections(src, dest);
//        		} else if (value instanceof Any) {
////        			Any src = (Any)value;
////        			Any dest = (Any)destStyle.getValue(property.getName());
//            		// TODO
//        		} else {
//        			assertEquals("Bad value for property " + property.getName(), sourceStyle.getValue(property.getName()), destStyle.getValue(property.getName()));
//        		}
//        	}
        }
    }

    private void checkCellValue(OdfTableCell sourceCell, OdfTableCell destCell) {
    	if (sourceCell.getValueType() == null)
    		assertNull(destCell.getValueType());
    	else if (sourceCell.getValueType().equals("void"))
    		assertEquals("Cell values are not the same", sourceCell.getValueType(), destCell.getValueType());
    	else if (sourceCell.getValueType().equals("string")) {
    		if (propertyMap.containsKey(sourceCell.getStringValue())) {
    			Object sourceValue = propertyMap.get(sourceCell.getStringValue());
	    		if (destCell.getValueType().equals("float"))
	        		assertEquals("Cell values are not the same", sourceValue, destCell.getDoubleValue());
	    		else if (destCell.getValueType().equals("percentage"))
	        		assertEquals("Cell values are not the same", sourceValue, destCell.getPercentageValue());
	    		else if (destCell.getValueType().equals("boolean"))
	        		assertEquals("Cell values are not the same", sourceValue, destCell.getBooleanValue());
	    		else if (destCell.getValueType().equals("currency"))
	        		assertEquals("Cell values are not the same", sourceValue, destCell.getCurrencyValue());
	    		else if (destCell.getFormula() != null)
	        		assertEquals("Cell values are not the same", sourceValue, destCell.getFormula());
    		} else {
        		assertEquals("Cell values are not the same", sourceCell.getStringValue(), destCell.getStringValue());
    		}
    	}
    }


    private void checkCellValue(Object value, OdfTableCell destCell) {
		if (value instanceof Double) {
			assertEquals("Cell value is incorrect", value, destCell.getDoubleValue());
		} else if (value instanceof BigDecimal) {
			assertEquals("Cell value is incorrect", ((BigDecimal) value).doubleValue(), destCell.getDoubleValue());
		} else if (value instanceof Date) {
			assertEquals("Cell value is incorrect", ((Date) value).toString(), destCell.getDateValue().toString());
		} else if (value instanceof Calendar) {
			assertEquals("Cell value is incorrect", (Calendar) value, destCell.getDateValue());
		} else if (value instanceof Integer) {
			assertEquals("Cell value is incorrect", value, destCell.getDoubleValue().intValue());
		} else if (value instanceof Long) {
			assertEquals("Cell value is incorrect", value, destCell.getDoubleValue().longValue());
		} else if (value instanceof String) {
			assertEquals("Cell value is incorrect", value, destCell.getStringValue());
		}
    }
}
